<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Management Console</title>
    <style>
      *,
      html,
      body {
        margin: 0;
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
          'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
          'Helvetica Neue', sans-serif;
        font-size: 16px;
        color: #52524f;
      }

      header,
      footer {
        display: flex;
        align-items: center;
        justify-content: center;
        height: 80px;
      }

      h1 {
        font-size: 2.5rem;
        vertical-align: middle;
      }

      svg {
        width: 100vw;
        height: calc(100vh - 80px);
      }
    </style>
  </head>

  <body>
    <script src="https://cdn.jsdelivr.net/lodash/4.14.0/lodash.min.js"></script>
    <script src="https://d3js.org/d3.v4.min.js"></script>

    <!-- These script are kept to dive deep into force layout of D3, if needed. -->
    <!--<script src='//d3js.org/d3-collection.v1.js'></script>-->
    <!--<script src='//d3js.org/d3-selection.v1.js'></script>-->
    <!--<script src='//d3js.org/d3-timer.v1.js'></script>-->
    <!--<script src='//d3js.org/d3-dispatch.v1.js'></script>-->
    <!--<script src='//d3js.org/d3-quadtree.v1.js'></script>-->
    <!--<script src='https://d3js.org/d3-drag.v1.js'></script>-->
    <!--<script src='//d3js.org/d3-force.v1.js'></script>-->

    <script src="/socket.io/socket.io.js"></script>
    <script>
      var socket = io.connect('/monitoring')
    </script>

    <header>
      <h1>Cote Monitor</h1>
    </header>
    <main>
      <div class="svg"></div>
    </main>
    <script>
      const SVGDimensions = getSVGDimensions(document, window)

      const config = {
        /* SVG variables */
        svgElmSpecifier: '.svg',
        svgWidth: SVGDimensions.svgWidth,
        svgHeight: SVGDimensions.svgHeight - 80, // 80px is the header size

        /* Hosts radius */
        host: {
          radius: 15,
          color: '#FFEE93',
          textColor: '#D4C266',
        },
        process: {
          radius: 10,
          color: '#A0CED9',
          textColor: '#8FBBC5',
        },
        node: {
          radius: 5,
          color: '#ADF7B6',
          textColor: '#9EC3A3',
        },

        /* Text element variables */
        textDx: '14',
        textDy: '.31rem',

        /********************************/
        /* Force layout configuration */
        /********************************/
        strength: 50, // charge
        distanceMin: 20, // distance min btw nodes

        /* Link configuration */
        linkDistance: {
          hostProcess: 40,
          processNode: 40,
          nodeNode: 40,
        },

        linkStrength: {
          hostProcess: 1,
          processNode: 1,
          nodeNode: 1,
        },
        linkIterations: 30,

        linkColor: '#f1db6b',

        collide: {
          offset: 8,
          iteration: 16,
        },
      }

      const { svgWidth, svgHeight } = config

      const svg = d3
        .select(config.svgElmSpecifier)
        .append('svg')
        .attr('width', svgWidth)
        .attr('height', svgHeight)

      let nodes = svg.selectAll('.node')
      let links = svg.selectAll('.link')

      const simulation = d3
        .forceSimulation()
        .force('charge', d3.forceManyBody())
        .force('collide', configureCollisionDetection(config))
        .force('link', configureLink(config))
        .force('center', d3.forceCenter(svgWidth / 2, svgHeight / 2))
        .on('tick', ticked)

      const drag = d3
        .drag()
        .on('start', dragstarted)
        .on('drag', dragged)
        .on('end', dragended)

      let alpha = 0.3

      function update(data) {
        // Update links
        links = svg.selectAll('.link').data(data.links)

        // Exit unused links
        links.exit().remove()

        // Enter new links
        const linkEnter = links
          .enter()
          .append('line')
          .classed('link', true)
          .style('stroke', config.linkColor)

        links = linkEnter.merge(links)

        // Update Nodes
        nodes = svg.selectAll('.node').data(data.nodes, d => d.id)

        // Exit unused nodes.
        nodes.exit().remove()

        // Add new nodes
        const nodeEnter = nodes
          .enter()
          .append('g')
          .classed('node', true)
          .call(drag)

        nodeEnter
          .append('circle')
          .attr('r', d => config[d.type].radius)
          .attr('fill', d => config[d.type].color)

        nodeEnter
          .append('text')
          .classed('text', true)
          .attr('x', config.textDx)
          .attr('y', config.textDy)
          .style('fill', d => config[d.type].textColor)
          .text(d => d.name)

        nodes = nodeEnter.merge(nodes)

        data.nodes.forEach(node => (node.r = config[node.type].radius))

        simulation.nodes(data.nodes)

        simulation.force('link').links(data.links)

        simulation.alphaTarget((alpha = alpha * 0.1)).restart()
      }

      function ticked() {
        links
          .attr('x1', d => d.source.x)
          .attr('y1', d => d.source.y)
          .attr('x2', d => d.target.x)
          .attr('y2', d => d.target.y)

        nodes.attr('transform', d => {
          const radius = config[d.type].radius
          const { svgWidth, svgHeight } = config

          let posX = Math.max(radius, Math.min(svgWidth - radius, d.x))
          let posY = Math.max(radius, Math.min(svgHeight - radius, d.y))
          return 'translate(' + posX + ',' + posY + ')'
        })
      }

      function dragstarted(d) {
        if (!d3.event.active) {
          simulation.alphaTarget(0.3).restart()
        }
        d.fx = d.x
        d.fy = d.y
      }

      function dragged(d) {
        d.fx = d3.event.x
        d.fy = d3.event.y
      }

      function dragended(d) {
        if (!d3.event.active) {
          simulation.alphaTarget(0)
        }

        d.fx = d3.event.x
        d.fy = d3.event.y
      }

      function isHostProcessConnection(d) {
        return (
          (d.source.type == 'host' && d.target.type == 'process') ||
          (d.target.type == 'host' && d.source.type == 'process')
        )
      }

      function isProcessNodeConnection(d) {
        return (
          (d.source.type == 'process' && d.target.type == 'node') ||
          (d.source.type == 'node' && d.target.type == 'process')
        )
      }

      function configureLink(config) {
        return d3.forceLink()
        //         .distance((d) => {
        //             let distance = 0;
        //     if (isHostProcessConnection(d)) {
        //         distance = config.linkDistance.hostProcess;
        //     } else if(isProcessNodeConnection(d)) {
        //         distance = config.linkDistance.processNode;
        //     }
        //     else distance = config.linkDistance.nodeNode;

        //     return Math.max(distance, config.distanceMin);
        // });
      }

      function configureCollisionDetection(config) {
        return d3
          .forceCollide(d => d.r * 4 + config.collide.offset)
          .iterations(config.collide.iteration)
      }

      function injectPreviousLocations(data, simulation) {
        const newData = Object.assign([], data)

        _.forEach(newData.nodes, node => {
          const oldNode = _.filter(
            simulation.nodes(),
            simulationNode => simulationNode.id == node.id
          )

          if (oldNode.length == 1) {
            node.x = oldNode[0].x
            node.y = oldNode[0].y
            node.fx = oldNode[0].fx ? oldNode[0].fx : null
            node.fy = oldNode[0].fy ? oldNode[0].fy : null
          }
        })

        return newData
      }

      socket.on('statusUpdate', data => {
        alpha = 0.3
        const newData = injectPreviousLocations(data, simulation)
        update(newData)
      })

      function getSVGDimensions(document, window) {
        const cWidth = document.documentElement.clientWidth
        const cHeight = document.documentElement.clientHeight
        const viewportWidth = Math.max(cWidth, window.innerWidth || 0)
        const viewportHeight = Math.max(cHeight, window.innerHeight || 0)

        return {
          svgWidth: viewportWidth,
          svgHeight: viewportHeight,
        }
      }

      window.addEventListener('resize', () => {
        const SVGDimensions = getSVGDimensions(document, window)

        config.svgWidth = SVGDimensions.svgWidth
        config.svgHeight = SVGDimensions.svgHeight

        const { svgWidth, svgHeight } = config
        const center = d3.forceCenter(svgWidth / 2, svgHeight / 2)

        simulation.force('center', center)
      })
    </script>
  </body>
</html>
